package com.github.dabai.aggregation;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.beanutils.PropertyUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class Aggregation<I extends IAggregatingInput, O extends IAggregatingOut> {
	private List<AggregationField<I>> aggregations = Lists.newArrayList();
	private List<AggregationPostField<I>> posts = Lists.newArrayList();
	private List<GroupByField<?>> groupBy = Lists.newArrayList();
	private Class<O> outClass;
	private IAggregationConvert<I, O> convert = new IAggregationConvert.CommonAggregationConvert<I, O>();

	public Aggregation<I, O> outClass(Class<O> outClass) {
		this.outClass = outClass;
		return this;
	}

	@SuppressWarnings("rawtypes")
	public Aggregation<I, O> addGroupBy(String name, String mapppingName) {
		GroupByField g = new GroupByField(name, mapppingName);
		this.groupBy.add(g);
		return this;
	}

	@SuppressWarnings("rawtypes")
	public Aggregation<I, O> addGroupBy(String name, String mapppingName, String dateFormatPattern) {
		GroupByField g = new GroupByField(name, mapppingName, dateFormatPattern);
		this.groupBy.add(g);
		return this;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public Aggregation<I, O> addGroupBy(String name, String mapppingName, IGroupByFieldConvert<?> convert) {
		GroupByField g = new GroupByField(name, mapppingName, convert);
		this.groupBy.add(g);
		return this;
	}

	public Aggregation<I, O> addAggregationField(String fieldName, Object defaultValue, IAggregation<I> aggregation) {
		aggregations.add(new AggregationField<I>(fieldName, defaultValue, aggregation));
		return this;
	}

	public Aggregation<I, O> addPostAggregationField(String fieldName, Object defaultValue,
			IAggregationPost<I> aggregation) {
		posts.add(new AggregationPostField<I>(fieldName, defaultValue, aggregation));
		return this;
	}

	public Aggregation<I, O> convert(IAggregationConvert<I, O> convert) {
		this.convert = convert;
		return this;
	}

	public List<O> aggregating(List<I> dataList) throws Exception {
		List<O> result = Lists.newArrayList();
		Map<GroupByKey, Map<String, AggregationField<I>>> aggregationMap = Maps.newHashMap();
		Map<GroupByKey, Map<String, AggregationPostField<I>>> postMap = Maps.newHashMap();
		Map<GroupByKey, List<I>> dataMap = groupBy(dataList, groupBy);
		Set<GroupByKey> keys = dataMap.keySet();
		for (GroupByKey k : keys) {
			Map<String, AggregationField<I>> afList = aggregationMap.get(k);
			Map<String, AggregationPostField<I>> bfList = postMap.get(k);
			if (afList == null) {
				afList = Maps.newHashMap();
				aggregationMap.put(k, afList);
			}
			if (bfList == null) {
				bfList = Maps.newHashMap();
				postMap.put(k, bfList);
			}
			for (AggregationField<I> a : aggregations) {
				AggregationField<I> aclone = new AggregationField<I>(a.getName(), a.getResult(), a.getAggregation());
				afList.put(a.getName(), aclone);
			}
			for (AggregationPostField<I> a : posts) {
				AggregationPostField<I> aclone = new AggregationPostField<I>(a.getName(), a.getResult(),
						a.getAggregation());
				bfList.put(a.getName(), aclone);
			}
		}
		for (GroupByKey k : keys) {
			Map<String, AggregationField<I>> afList = aggregationMap.get(k);
			Map<String, AggregationPostField<I>> bfList = postMap.get(k);
			List<I> datas = dataMap.get(k);
			for (I data : datas) {
				for (AggregationField<I> aggregationField : afList.values()) {
					aggregationField.aggregating(data);
				}
			}
			for (AggregationPostField<I> post : bfList.values()) {
				post.aggregating(k, afList);
			}
			O o = convert.convert(k, afList, bfList, outClass);
			result.add(o);
		}
		return result;
	}

	private Map<GroupByKey, List<I>> groupBy(List<I> datas, List<GroupByField<?>> fields) throws Exception {
		Map<GroupByKey, List<I>> result = Maps.newHashMap();
		for (I data : datas) {
			GroupByKey key = new GroupByKey();
			for (GroupByField<?> field : fields) {
				String f = field.getName();
				Object v = PropertyUtils.getProperty(data, f);
				key.add(field, v);
			}
			List<I> list = result.get(key);
			if (list == null) {
				list = Lists.newArrayList();
				result.put(key, list);
			}
			list.add(data);
		}
		return result;
	}

}
